package model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

import exceptions.AlternatieveSleutelBestaatException;
import exceptions.AttribuutBestaatException;
import exceptions.AttribuutBestaatNietException;
import exceptions.AttribuutNaamKomtNietVoorInPrimaireSleutelException;
import exceptions.BuitenlandseSleutelVerwijstNaarNietBestaandRecordException;
import exceptions.FilterIsLeegException;
import exceptions.LijstenNietEvenLangException;
import exceptions.PrimaireSleutelAttribuutNamenException;
import exceptions.PrimaireSleutelDuplicaatException;
import exceptions.RecordWaardeMagNietLeegZijn;
import exceptions.ReferentieBestaatException;
import exceptions.ReferentieBestaatNietException;
import exceptions.SoortIncorrectException;
import exceptions.SoortOnbekendException;
import exceptions.RecordWaardeIsLeegException;
import exceptions.TabelBestaatNietException;

public class Tabel {
	private String databankNaam;
	private String naam;
	private ArrayList<Record> records;
	private PrimaireSleutel primaireSleutel;
		
	private HashMap<String, Attribuut> attributen;
	private HashMap<String, AlternatieveSleutel> alternatieveSleutels;
	private HashMap<String, Referentie> referenties;
	
	public Tabel(String databankNaam, String naam) {
		this.databankNaam = databankNaam;
		this.naam = naam;
		
		this.records = new ArrayList<Record>();
		
		this.attributen = new HashMap<String, Attribuut>();
		this.alternatieveSleutels = new HashMap<String, AlternatieveSleutel>();
		this.referenties = new HashMap<String, Referentie>();
	}
	
	public void addRecord(String[] namen, String[] waardes) throws AttribuutBestaatNietException, SoortOnbekendException, SoortIncorrectException, RecordWaardeIsLeegException, RecordWaardeMagNietLeegZijn, PrimaireSleutelAttribuutNamenException, AttribuutNaamKomtNietVoorInPrimaireSleutelException, PrimaireSleutelDuplicaatException, BuitenlandseSleutelVerwijstNaarNietBestaandRecordException {
		//Check of keys bestaan in tabel
		for(int i=0; i<namen.length; i++) {
			if(!attributen.containsKey(namen[i])) {
				throw new AttribuutBestaatNietException("Attribuut " + namen[i] + " bestaat niet in tabel " + this.naam);
			}
		}

		//Check of typen van waardes kloppen, throws SoortOnbekendException, SoortIncorrectException
		for(int i=0; i<waardes.length; i++) {
			if(!attributen.get(namen[i]).isSoortCorrect(waardes[i])) {
				throw new SoortIncorrectException("Attribuut " + namen[i] + " bevat een waarde met een incorrecte soort");
			}
		}
		
		//Checken of ze wel of niet null mogen zijn als waarde null is
		for(int i=0; i<waardes.length; i++) {
			String waarde = waardes[i];
			if(waarde == null || waarde.equals("")) {
				throw new RecordWaardeIsLeegException("Waarde is leeg");
			}
			if(waarde.equals("NULL")) {
				if(!Leeg.contains(waarde).equals(attributen.get(namen[i]).getNul().name())) {
					throw new RecordWaardeMagNietLeegZijn("Record waarde met naam " + namen[i] + " mag niet leeg zijn");
				}
			}
		}

		//Check primairykey
		if(!primaireSleutel.checkAttribuutNaam(namen)) {
			throw new PrimaireSleutelAttribuutNamenException("Niet alle attributen uit de primaire sleutel komen voor in de opgegeven attributen");
		}
		for(Record record: records) {
			if(primaireSleutel.check(record, namen, waardes)) {
				throw new PrimaireSleutelDuplicaatException("Er bestaat al een record met deze primaire sleutel");
			}
		}
		
		//Check foreignkey
		//Stap1: check of er foreignkeys zijn
		if(!referenties.isEmpty()) {
			//doe voor alle foreignkeys
			Referentie[] refs = referenties.values().toArray(new Referentie[0]);
			for(int i=0; i<refs.length; i++) {
				
				//Stap2: Zoek Records op die een overeenkomst hebben in het eerste attribuut van de foreignkeygroep
				Tabel naarGroepTabel = refs[i].getNaarPrimaireSleutel().getTabel();
				String filterKey = refs[i].getVanafGroep().getAttributen().get(0).getNaam();
				
				String filterValue = null;
				for(int j=0; j<namen.length; j++) {
					if(namen[j].equals(filterKey)) {
						filterValue = waardes[j];
					}
				}
				
				ArrayList<Record> filteredRecords = naarGroepTabel.getRecords(filterKey, filterValue);
				
				//Stap3: Doe de check zelf
				if(filteredRecords.isEmpty()) {
					//WRONG Dit mag dus niet! throw Exception dat foreignkey verwijst naar niet bestaand record
					throw new BuitenlandseSleutelVerwijstNaarNietBestaandRecordException("In Tabel " + naarGroepTabel.getNaam() + " moet een Record zijn met attribuut " + filterKey + " op waarde " + filterValue);
				} 
			}
		}
		
		//Voeg ook velden toe aan records die niet ingevuld waren
		String[] attribuutNamen = attributen.keySet().toArray(new String[0]);
		for(int i=0; i<namen.length; i++) {
			for(int j=0; j<attribuutNamen.length; j++) {
				if(namen[i].equals(attribuutNamen[j])) {
					attribuutNamen[j] = null;
				}
			}
		}
		
		String[] nieuweNamen = new String[attributen.size()];
		String[] nieuweWaardes = new String[attributen.size()];
		
		for(int i=0; i<namen.length; i++) {
			nieuweNamen[i] = namen[i];
			nieuweWaardes[i] = waardes[i];
		}
		int index = 0;
		for(int i=0; i<attribuutNamen.length; i++) {
			if(attribuutNamen[i] != null) {
				nieuweNamen[namen.length + index] = attribuutNamen[i];
				nieuweWaardes[namen.length + index] = null;
				index++;
			}
		}

		records.add(new Record(nieuweNamen, nieuweWaardes, records.size()+1));
	}
	
	public void deleteRecords(String filterKey, String filterValue) throws AttribuutBestaatNietException, FilterIsLeegException, TabelBestaatNietException, BuitenlandseSleutelVerwijstNaarNietBestaandRecordException, ReferentieBestaatNietException {
		ArrayList<Record> toRemove = new ArrayList<Record>();	
		if(filterKey != null && !filterKey.equals("")) {
			//delete records where....
			if(filterValue == null || filterValue.equals("")) {
				throw new FilterIsLeegException("Filterwaarde is leeg.");
			}
			getAttribuut(filterKey);//Om te checken of het attribuut van het filter bestaat, throws AttribuutBestaatNietException
				
			for(Record record: records) {
				//check where....
				if(record.getRecord(filterKey).equals(filterValue)) {
					//delete it!
					toRemove.add(record);
				}
			}
		} else {
			//delete ALL the records!
			toRemove = (ArrayList<Record>) records.clone();
		}
		
		//Check foreign keys
		Databank databank = MyRDBMS.getDatabank(databankNaam);
		String[] tabelNamen = databank.getTabellenHashMapKeys();
		for(int i=0; i<tabelNamen.length; i++) {
			Tabel tabel = databank.getTabel(tabelNamen[i]);

			if(!tabel.isReferentiesLeeg()) {
				
				//doe voor alle foreignkeys
				String[] referentieNamen = tabel.getReferentiesHashMapKeys();
				for(int j=0; j<referentieNamen.length; j++) {
					
					//kijk of referentie naar deze tabel leid
					if(tabel.getReferentie(referentieNamen[j]).getNaarPrimaireSleutel().getTabel().getNaam().equals(this.naam)) {
						
						//controlleer ieder record dat je gaat verwijderen
						for(Record recordToRemove: toRemove) {
							
							//controlleer alle attributen in de primairesleutel
							for(Attribuut attribuut: primaireSleutel.getAttributen()) {
								String filterKey2 = attribuut.getNaam();
								String filterValue2 = recordToRemove.getRecord(filterKey2);
								
								//doe de controle zelf op dit record en dit attribuut
								if(!tabel.getRecords(filterKey2, filterValue2).isEmpty()) {
									throw new BuitenlandseSleutelVerwijstNaarNietBestaandRecordException("Record waarbij " + filterKey2 + " = " + filterValue2 + " bestaat in de verwijzende tabel " + tabel.getNaam());
								}
							}
						}
					}
				}
			}
		}
		
		records.removeAll(toRemove);
	}
	
	public void updateRecords(String[] namen, String[] waardes, String filterKey, String filterValue) throws AttribuutBestaatNietException, SoortIncorrectException, RecordWaardeMagNietLeegZijn, SoortOnbekendException, RecordWaardeIsLeegException, FilterIsLeegException, LijstenNietEvenLangException, PrimaireSleutelAttribuutNamenException, PrimaireSleutelDuplicaatException, AttribuutNaamKomtNietVoorInPrimaireSleutelException, ReferentieBestaatNietException, BuitenlandseSleutelVerwijstNaarNietBestaandRecordException, TabelBestaatNietException {
		//Check of keys bestaan in tabel
		for(int i=0; i<namen.length; i++) {
			if(!attributen.containsKey(namen[i])) {
				throw new AttribuutBestaatNietException("Attribuut " + namen[i] + " bestaat niet in tabel " + this.naam);
			}
		}

		//Check of typen van waardes kloppen, throws SoortOnbekendException, SoortIncorrectException
		for(int i=0; i<waardes.length; i++) {
			if(!attributen.get(namen[i]).isSoortCorrect(waardes[i])) {
				throw new SoortIncorrectException("Attribuut " + namen[i] + " bevat een waarde met een incorrecte soort");
			}
		}

		//Checken of ze wel of niet null mogen zijn als waarde null is
		for(int i=0; i<waardes.length; i++) {
			String waarde = waardes[i];
			if(waarde == null || waarde.equals("")) {
				throw new RecordWaardeIsLeegException("Waarde is leeg");
			}
			if(waarde.equals("NULL")) {
				if(!Leeg.contains(waarde).equals(attributen.get(namen[i]).getNul().name())) {
					throw new RecordWaardeMagNietLeegZijn("Record waarde met naam " + namen[i] + " mag niet leeg zijn");
				}
			}
		}
		
		//Zoek records op vanuit filter
		ArrayList<Record> toUpdate = new ArrayList<Record>();
		if(filterKey != null && !filterKey.equals("")) {
			//update records where....
			if(filterValue == null || filterValue.equals("")) {
				throw new FilterIsLeegException("Filterwaarde is leeg.");
			}
			getAttribuut(filterKey);//Om te checken of het attribuut van het filter bestaat, throws AttribuutBestaatNietException
				
			for(Record record: records) {
				//check where....
				if(record.getRecord(filterKey).equals(filterValue)) {
					toUpdate.add(record);
				}
			}
		} else {
			//Update ALL the records!
			toUpdate.addAll(records);
		}
		
		for(Record recordToUpdate: toUpdate) {
			//Pak eerst alle attribuutnamen
			String[] namenToCheck = attributen.keySet().toArray(new String[0]);
			String[] waardesToCheck = new String[attributen.size()];
			
			//Pak alle oude waardes per attribuutnaam
			for(int i=0; i<waardesToCheck.length; i++) {
				waardesToCheck[i] = recordToUpdate.getRecord(namenToCheck[i]);
			}
			
			//Wijzig alle te vernieuwen waardes in deze lijst
			for(int i=0; i<namen.length; i++) {
				for(int j=0; j<namenToCheck.length; j++) {
					if(namen[i].equals(namenToCheck[j])) {
						waardesToCheck[j] = waardes[i];
					}
				}
			}
			
			//Check primairykey
			for(Record record: records) {
				if(record.getId() != recordToUpdate.getId()) {
					if(primaireSleutel.check(record, namenToCheck, waardesToCheck)) {
						throw new PrimaireSleutelDuplicaatException("Er bestaat al een record met deze primaire sleutel");
					}
				}
			}
			
			//Check foreignkey
			//Voor VanafGroep kant:
			if(!referenties.isEmpty()) {
				//doe voor alle foreignkeys
				String[] referentieNamen = getReferentiesHashMapKeys();
				for(int i=0; i<referentieNamen.length; i++) {
					
					//Vergelijk ieder te vernieuwen attribuut met de attributen van de primkey
					for(int j=0; j<namen.length; j++) {
						PrimaireSleutel primkey = getReferentie(referentieNamen[i]).getNaarPrimaireSleutel();
						for(Attribuut attribuut: primkey.getAttributen()) {
							if(attribuut.getNaam().equals(namen[j])) {
								
								Tabel naarTabel = getReferentie(referentieNamen[i]).getNaarPrimaireSleutel().getTabel();
								ArrayList<Record> results = naarTabel.getRecords(namen[j], waardes[j]);
								
								if(results.isEmpty()) {
									throw new BuitenlandseSleutelVerwijstNaarNietBestaandRecordException("Record bestaat niet in tabel " + naarTabel.getNaam());
								}
							}
						}
					}
				}
			}
			//Voor NaarGroep kant:
			//Vergelijk ieder te vernieuwen attribuut met de attributen van de primkey
			for(int i=0; i<namen.length; i++) {
				for(Attribuut attribuut: primaireSleutel.getAttributen()) {
					if(attribuut.getNaam().equals(namen[i])) {
						
						//Loop door alle tabellen
						Databank databank = MyRDBMS.getDatabank(databankNaam);
						String[] tabelNamen = databank.getTabellenHashMapKeys();
						for(int j=0; j<tabelNamen.length; j++) {
							Tabel tabel = databank.getTabel(tabelNamen[j]);

							if(!tabel.isReferentiesLeeg()) {
								
								//doe voor alle foreignkeys
								String[] referentieNamen = tabel.getReferentiesHashMapKeys();
								for(int k=0; k<referentieNamen.length; k++) {
									
									//kijk of referentie naar deze tabel leid
									if(tabel.getReferentie(referentieNamen[k]).getNaarPrimaireSleutel().getTabel().getNaam().equals(this.naam)) {
										
										//controlleer ieder record dat je gaat updaten
										for(Record toUpdateRecord: toUpdate) {
											
											//controlleer alle attributen in de primairesleutel
											for(Attribuut pkeyAttribuut: primaireSleutel.getAttributen()) {
												String filterKey2 = pkeyAttribuut.getNaam();
												String filterValue2 = toUpdateRecord.getRecord(filterKey2);
												
												//doe de controle zelf op dit record en dit attribuut
												if(!tabel.getRecords(filterKey2, filterValue2).isEmpty()) {
													throw new BuitenlandseSleutelVerwijstNaarNietBestaandRecordException("Record waarbij " + filterKey2 + " = " + filterValue2 + " bestaat in de verwijzende tabel " + tabel.getNaam());
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
			
			//Update Records
			recordToUpdate.update(namen, waardes);
		}
	}
	
	public void addAttribuut(Attribuut attribuut) throws AttribuutBestaatException {
		if (!attributen.containsKey(attribuut.getNaam())) {
			attributen.put(attribuut.getNaam(), attribuut);
		} else {
			throw new AttribuutBestaatException("Attribuut " + attribuut.getNaam() + " bestaat al in tabel " + naam);
		}
	}
	
	public void addAlternatieveSleutels(AlternatieveSleutel alternatieveSleutel) throws AlternatieveSleutelBestaatException {
		if (alternatieveSleutels.containsKey(alternatieveSleutel.getGroepNaam())) {
			alternatieveSleutels.put(alternatieveSleutel.getGroepNaam(), alternatieveSleutel);
		} else {
			throw new AlternatieveSleutelBestaatException("AlternatieveSleutel " + alternatieveSleutel.getGroepNaam() + " bestaat al in tabel " + naam);
		}
	}
	
	public void addReferentie(Referentie referentie) throws ReferentieBestaatException {
		if (!referenties.containsKey(referentie.getNaam())) {
			referenties.put(referentie.getNaam(), referentie);
		} else {
			throw new ReferentieBestaatException("Referentie " + referentie.getNaam() + " bestaat al in tabel " + naam);
		}
	}
	
	public void deleteReferentie(String key) throws ReferentieBestaatNietException {
		if (referenties.containsKey(key)) {
			referenties.remove(key);
		} else {
			throw new ReferentieBestaatNietException("Referentie " + key + " bestaat niet in tabel " + naam);
		}
	}

	public String getDatabankNaam() {
		return databankNaam;
	}

	public void setDatabankNaam(String databankNaam) {
		this.databankNaam = databankNaam;
	}

	public String getNaam() {
		return naam;
	}

	public void setNaam(String naam) {
		this.naam = naam;
	}

	public PrimaireSleutel getPrimaireSleutel() {
		return primaireSleutel;
	}

	public void setPrimaireSleutel(PrimaireSleutel primaireSleutel) {
		this.primaireSleutel = primaireSleutel;
	}
	
	public String[] getReferentiesHashMapKeys() {
		return referenties.keySet().toArray(new String[0]);
	}
	
	public Referentie getReferentie(String naam) throws ReferentieBestaatNietException {
		Referentie referentie = referenties.get(naam);
		if (referentie == null) {
			throw new ReferentieBestaatNietException("Referentie " + naam + " bestaat niet in Tabel " + this.naam);
		}		
		return referentie;
	}
	
	public boolean isReferentiesLeeg() {
		return this.referenties.isEmpty();
	}
	
	public String[] getAttributenHashMapKeys() {
		return attributen.keySet().toArray(new String[0]);
	}
	
	public Attribuut getAttribuut(String naam) throws AttribuutBestaatNietException {
		Attribuut attribuut = attributen.get(naam);
		if (attribuut == null) {
			throw new AttribuutBestaatNietException("Attribuut " + naam + " bestaat niet in Tabel " + this.naam);
		}
		return attribuut;
	}
	
	public String[] getAttribuutNamen() {
		return (String[]) attributen.keySet().toArray(new String[0]);
	}
	
	public String printTabelInfo() {
		String output = "Tabel: " + naam + " = {";
		for(Attribuut attr : attributen.values()) {
			output += attr.getNaam() + " | ";
		}
		output += "}";
		return output;
	}

	public ArrayList<Record> getRecords(String filterKey, String filterValue) throws AttribuutBestaatNietException {
		ArrayList<Record> result = new ArrayList<Record>();
		if(filterKey != null && !filterKey.equals("") && filterValue != null && !filterValue.equals("")) {
			for(Record record : records) {
				if(record.getRecord(filterKey).equals(filterValue)) {
					result.add(record);
				}
			}
		} else {
			result = records;
		}
		return result;
	}
	
	public ArrayList<Record> getRecords() throws AttribuutBestaatNietException {
		return getRecords(null, null);
	}
}
